package GUI;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import presenter.*;

/**
 * 
 * @author Benjamin GOUIT et David SOLO RABRIMANANA
 *
 */
public class ChatGUI extends JFrame implements Runnable, FromUser, ToUser {
	
	private static final long serialVersionUID = 1L;

	private ChatPresenter chatPres;
	
	private JLabel messLogin;
	private JTextArea textLogin;
	private JButton bConnect;

	private JTabbedPane tabbedPaneListMess;
	private JTextArea listMess;
	private JScrollPane scrollPaneListMess;

	private JList listUser;
	private Vector<String> listUserTab;
	private JScrollPane scrollPaneListUser;

	private JTextArea messArea;
	private JScrollPane scrollPaneMessArea;
	
	private JButton bSendFile;
	private JFileChooser fileChooserBox;
	private JButton bDisconnect;
	
	private JPanel plistMess;
	private JPanel plistUser;
	private JPanel pmessArea;
	private JPanel pbSendFile;
	private JPanel pbDisconnect;
	private JPanel p6;
	
	private boolean connected = false;
	
	private Vector<String[]> listConvers;
	
	/**
	 * Constructeur
	 */
	public ChatGUI () {
		// create the components
		messLogin = new JLabel("login :");
		
		textLogin = new JTextArea();
		textLogin.addKeyListener(new KeyListener() {
			
			@Override
			public void keyTyped(KeyEvent arg0) {}
			
			@Override
			public void keyReleased(KeyEvent arg0) {}
			
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					Connect ();
				}
			}
		});
		
		bConnect = new JButton("Connect");
		bConnect.addActionListener(new ConnectListener ());
		
		// create the components
		listMess = new JTextArea("Hello ! :)\nYou are connected. Good luck ! :p");
		listMess.setEditable(false);
		listMess.setLineWrap(true); //retour à ligne automatique
		listMess.setWrapStyleWord(true); //pour que les mots ne soient pas coupés
		scrollPaneListMess = new JScrollPane(listMess,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		tabbedPaneListMess = new JTabbedPane ();
		tabbedPaneListMess.add("All", scrollPaneListMess);
		tabbedPaneListMess.addChangeListener(new ChangeListener () {
			@Override
			public void stateChanged(ChangeEvent arg0) {
				selectGoodMembers ();
			}
		});
		
		listConvers = new Vector<String[]>();
		
		listUserTab = new Vector<String> ();
		listUser = new JList(listUserTab);
		scrollPaneListUser = new JScrollPane(listUser,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		
		messArea = new JTextArea();
		messArea.addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(KeyEvent arg0) {}
			@Override
			public void keyReleased(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER)
					messArea.setText(""); //Effacement du texte dans la messArea après l'avoir envoyé
			}
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					Send ();
				}
			}
		});
		messArea.setLineWrap(true); //retour à ligne automatique
		messArea.setWrapStyleWord(true); //pour que les mots ne soient pas coupés
		scrollPaneMessArea = new JScrollPane(messArea,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		
		bSendFile = new JButton("Send File");
		bSendFile.addActionListener(new SendFileListener ());
		
		bDisconnect = new JButton("Disconnect");
		bDisconnect.addActionListener(new DisconnectListener ());
	}
	
	/**
	 * Constructeur
	 * @param chatPres
	 */
	public ChatGUI (ChatPresenter chatPres) {
		this.chatPres = chatPres;
	}
	
	/**
	 * Setter du ChatPresenter
	 * @param cp
	 */
	public void setChatPres (ChatPresenter cp) {
		chatPres = cp;
	}

	/**
	 * Lanceur du thread GUI
	 */
	@Override
	public void run() {
		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		initComponentsLogin ();
		
		while (!connected) {
			try {
				Thread.sleep(500L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		initComponentsConnected ();
		
		while (connected) {
			try {
				Thread.sleep(500L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		connected = true;
		listMess.setText(listMess.getText()+"\nYou are disconnected. See you soon !");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		this.dispose();
	}

	/**
	 * Affichage de la fenêtre de connexion
	 */
	private void initComponentsLogin () {		
		this.setLayout(new BorderLayout());
		 // places the components in the layout
		 this.add("West",messLogin);
		 this.add("Center",textLogin);
		 this.add("South",bConnect);
		 
		 this.setSize(300, 200);
		// the JFrame is visible now
		this.setVisible(true);		
	}
	
	/**
	 * Affichage de la fenêtre principale
	 */
	private void initComponentsConnected() {
		//pour envoyer un goodbye et fermer correctement le socket lorsqu'on ferme brusquement la fenêtre
		this.addWindowListener(new WindowListener () {
			@Override
			public void windowActivated(WindowEvent arg0) {}
			@Override
			public void windowClosed(WindowEvent arg0) {}

			@Override
			public void windowClosing(WindowEvent arg0) {
				Disconnect ();
			}
			@Override
			public void windowDeactivated(WindowEvent arg0) {}
			@Override
			public void windowDeiconified(WindowEvent arg0) {}
			@Override
			public void windowIconified(WindowEvent arg0) {}
			@Override
			public void windowOpened(WindowEvent arg0) {}
		});
		
		cleanLayout ();
		this.setSize(700, 900);
		
		plistMess = new JPanel();
		plistUser = new JPanel();
		pmessArea = new JPanel();
		pbSendFile = new JPanel();
		pbDisconnect = new JPanel();
		p6 = new JPanel();
		
		plistMess.setLayout(new GridLayout(1, 1));
		plistMess.add(tabbedPaneListMess);
		plistUser.setLayout(new GridLayout(1, 1));
		plistUser.add(scrollPaneListUser);
		pmessArea.setLayout(new GridLayout(1, 1));
		pmessArea.add(scrollPaneMessArea);
		pbSendFile.setLayout(new GridLayout(1, 1));
		pbSendFile.add(bSendFile);
		pbDisconnect.setLayout(new GridLayout(1, 1));
		pbDisconnect.add(bDisconnect);
		
		this.add(plistMess);
		this.add(plistUser);
		this.add(pmessArea);
		this.add(pbSendFile);
		this.add(pbDisconnect);
		this.add(p6);
		
		setSizeComponents ();
		
		// the JFrame is visible now
		this.setVisible(true);
		//Pour redimensionner les panel lorsqu'on redimensionne la fenêtre
		this.addComponentListener(new ComponentListener () {
			@Override
			public void componentHidden(ComponentEvent arg0) {}
			@Override
			public void componentMoved(ComponentEvent arg0) {}
			@Override
			public void componentResized(ComponentEvent arg0) {
				setSizeComponents ();
			}
			@Override
			public void componentShown(ComponentEvent arg0) {}
			
		});
		
		setSizeComponents ();
	}
	
	/**
	 * Respect des dimensions des composants lors de redimensionnement de la fenêtre
	 */
	private void setSizeComponents () {
		//tous les composants sont directement lié avec la taille de la fenêtre et entre eux pour
		//qu'ils gardent bien leurs proportions
		int intervalle = 5;
		int bandeau = 30;
		int x = intervalle;
		int y = intervalle;
		int width = 2*this.getWidth()/3;
		int height = 3*this.getHeight()/5;
		plistMess.setBounds(x, y, width, height);
		
		x = plistMess.getX() + plistMess.getWidth() + intervalle;
		y = plistMess.getY();
		width = this.getWidth() - x - intervalle;
		height = 4*this.getHeight()/5;
		plistUser.setBounds(x, y, width, height);
		
		x = plistMess.getX();
		y = plistMess.getY() + plistMess.getHeight() + intervalle;
		width = plistMess.getWidth();
		height = this.getHeight() - y - intervalle - bandeau;
		pmessArea.setBounds(x, y, width, height);
		
		x = plistUser.getX();
		y = plistUser.getY() + plistUser.getHeight() + intervalle;
		width = plistUser.getWidth();
		height = (this.getHeight() - y - 2*intervalle - bandeau)/2;
		pbSendFile.setBounds(x, y, width, height);

		x = plistUser.getX();
		y = pbSendFile.getY() + pbSendFile.getHeight() + intervalle;
		pbDisconnect.setBounds(x, y, width, height);
	}
	
	/**
	 * Sélection des bons membres lorsqu'on change d'onglets
	 */
	private void selectGoodMembers () {
		String selectPanName = tabbedPaneListMess.getTitleAt(tabbedPaneListMess.getSelectedIndex());
		if (selectPanName.equals("All")) {
			listUser.clearSelection();
		}
		else {
			JTextArea textArea = selectedTextArea(selectPanName);
			int beginIndex = textArea.getText().indexOf(':') + 2;
			int endIndex = textArea.getText().indexOf('\n');
			String listMembers = textArea.getText().substring(beginIndex, endIndex);
			
			Vector<String> members = new Vector<String> ();
			beginIndex = 0;
			while (!listMembers.equals("")) {
				endIndex = listMembers.indexOf(',');
				if (endIndex == -1) {
					members.add(listMembers);
					listMembers = "";
				}
				else {
					members.add(listMembers.substring(beginIndex, endIndex));
					beginIndex = endIndex+2;
					listMembers = listMembers.substring(beginIndex);
				}
			}
			int[] indices = new int[members.size()];
			for (int i = 0; i < members.size(); i++) {
				indices[i] = -1;
				for (int j = 0; j < listUserTab.size(); j++)
					if (listUserTab.elementAt(j).equals(members.elementAt(i)))
						indices[i] = j;
			}
			listUser.setSelectedIndices(indices);
		}
	}

	/**
	 * Demande de connexion de l'utilisateur local
	 */
	@Override
	public void Connect() {
		chatPres.performConnect(UserName());
		Connected();
	}

	/**
	 * Demande de disconnexion de l'utilisateur local
	 */
	@Override
	public void Disconnect() {
		chatPres.performDisconnect();
		connected = false;
	}
	
	/**
	 * Récupération du texte tapé par l'utilisateur local
	 * @return texte
	 */
	@Override
	public String EnterText() {
		return messArea.getText();
	}
	
	/**
	 * Demande d'envoi du texte
	 */
	@Override
	public void Send() {
		//récupération de la liste des destinataires
		Object[] usersObj = listUser.getSelectedValues();
		String[] users = new String[usersObj.length];
		boolean broadcast;
		
		for (int i = 0; i < usersObj.length; i++)
			users[i] = (String) usersObj[i];

		if (users.length == 0) {
			users = new String[1];
			users[0] = "broadcast";
			broadcast = true;
		}
		else {
			for (int i = 0; i < users.length; i++)
				users[i] = users[i].substring(0, users[i].indexOf('@'));
			broadcast = false;
		}
		
		//récupération du message
		String message = messArea.getText();
		
		//envoi du message aux destinataires
		chatPres.textToSend (users, message);
		
		//Affichage du message chez soi
		String[] conversMembers = new String[usersObj.length];
		for (int i = 0; i < usersObj.length; i++)
			conversMembers[i] = (String) usersObj[i];
		
		textToPrintByName (message, conversMembers, broadcast, "Me");
	}

	/**
	 * Entrée du nom de l'utilisateur local
	 * @return nom de l'utilisateur local
	 */
	@Override
	public String UserName() {
		return textLogin.getText();		
	}
	
	/**
	 * Indication de la connexion de l'utilisateur local
	 */
	@Override
	public void Connected() {
		connected = true;
	}
	
	/**
	 * Mise a jour de la liste des utilisateurs
	 * @param username
	 */
	@Override
	public void MajListUser(String username) {
		listUserTab.add(username);
		listUser.setListData(listUserTab);
		selectGoodMembers ();
	}
	
	/**
	 * Affichage d'un nouvel utilisateur connecté
	 * @param username
	 */
	@Override
	public void PrintNewConnected(String user) {
		listMess.setText(listMess.getText()+"\nNew user connected : "+user);
	}

	/**
	 * Nouvel utilisateur connecté
	 * Mise a jour de la liste des utilisateurs
	 * et affichage du nouvel utilisateur
	 * @param user
	 */
	public void newUserConnected (String user) {
		if (user != null) {
			MajListUser(user);
			PrintNewConnected(user);
		}
		else
			System.out.println("Error ChatGUI:newUserConnected => user = null");
	}
	
	/**
	 * Indication de la déconnexion d'un utilisateur
	 * @param username
	 */
	public void userDisconnect(String username) {
		if (username != null) {
			System.out.println("user to remove = " + username);
			listUserTab.remove(username);
			listUser.setListData(listUserTab);
			UserDisconnected (username);
		}	
		else
			System.out.println("user to remove = null");
	}
	
	/**
	 * Indication de la déconnexion d'un utilisateur
	 * @param username
	 */
	@Override
	public void UserDisconnected (String username) {
		listMess.setText(listMess.getText()+"\nUser disconnected : "+username);
	}
	
	
	/**
	 *  Récupère la JTextArea de l'onglet tabName (nom de l'onglet)
	 * @param tabName
	 * @return
	 */
	private JTextArea selectedTextArea(String tabName) {
		int index = tabbedPaneListMess.indexOfTab(tabName);
        JScrollPane scrollPane = (JScrollPane) tabbedPaneListMess.getComponentAt(index);
        //permet la récupération du composant textArea dans le scroll
        JViewport viewPort = scrollPane.getViewport();
        return (JTextArea) viewPort.getView();
	}

	/**
	 * Affichage d'un message texte
	 *  conversMembers[0] est l'expéditeur
	 * @param message
	 * @param conversMembers
	 * @param broadcast
	 */
	public void textToPrint(String message, String[] conversMembers, boolean broadcast) {
		textToPrintByName (message, conversMembers, broadcast, conversMembers[0]);
	}
	
	/**
	 * Affichage du message reçu dans l'onglet correspondant
	 * creation d'un nouvel onglet si besoin
	 * @param message
	 * @param conversMembers
	 * @param broadcast
	 * @param sender
	 */
	private void textToPrintByName (String message, String[] conversMembers, boolean broadcast, String sender) {
		if (broadcast) {
			TextReceived(message, sender, selectedTextArea("All"));//onglet All en broadcast
		}
		else {
			String namePan = existed (conversMembers); //on regarde si l'onglet existe déjà
			if (namePan == null) {//S'il n'existe pas, on le crée et l'enregistre
				String[] convers = new String[conversMembers.length+2];
				convers[0] = new String ("");
				convers[0] += conversMembers[0].substring(0, conversMembers[0].indexOf('@'));
				convers[1] = new String ("");
				convers[1] += conversMembers[0];
				convers[2] = new String(conversMembers[0]);
				for (int i = 1; i < conversMembers.length; i++) {
					convers[i+2] = new String(conversMembers[i]);
					convers[0] += ", "+conversMembers[i].substring(0, conversMembers[i].indexOf('@'));
					convers[1] += ", "+conversMembers[i];
				}
				listConvers.addElement(convers);
				JTextArea listMess = new JTextArea("New chat with : "+convers[1]);
				listMess.setEditable(false);
				listMess.setLineWrap(true); //retour à la ligne automatique
				listMess.setWrapStyleWord(true); //pour que les mots ne soient pas coupés
				JScrollPane scrollPaneListMess = new JScrollPane(listMess,
		                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
		                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
				tabbedPaneListMess.add(convers[0], scrollPaneListMess);//ouverture de l'onglet
				TextReceived(message, sender, selectedTextArea(convers[0]));//écriture du message
			}
			else {//S'il existe, on l'écris directement dans le bon onglet
				TextReceived(message, sender, selectedTextArea(namePan));//écriture du message
			}
		}
	}
	
	/**
	 * Existance du salon de conversation
	 * @param conversMembers
	 * @return le nom si l'onglet correspondant existe déjà, null sinon
	 */
	private String existed (String[] conversMembers) {
		for (String[] list : listConvers) {
			if (conversMembers.length+2 == list.length) {
				int nbSame = 0;
				for (String member : conversMembers) {
					for (int i = 2; i < list.length; i++) {
						if (member.equals(list[i]))
							nbSame++;
					}
				}
				if (nbSame == conversMembers.length)
					return list[0];
			}
		}
		
		return null;
	}
	
	/**
	 * Affichage du texte reçu
	 * @param message
	 * @param username
	 * @param listMessToWrite
	 */
	@Override
	public void TextReceived(String message, String username, JTextArea listMessToWrite) {
		listMessToWrite.setText(listMessToWrite.getText()+"\n"+ username +" : "+message);
	}
	
	/**
	 * Selection d'un fichier a envoyer
	 */
	@Override
	public File SelectFile() {//ChooseBox pour savoir quel fichier on envoie
		File file = null;
		fileChooserBox = new JFileChooser();
		fileChooserBox.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fileChooserBox.setMultiSelectionEnabled(false);
		if (fileChooserBox.showOpenDialog(null)== 
		    JFileChooser.APPROVE_OPTION) {
			file = fileChooserBox.getSelectedFile();
		}
		
		return file;
	}
	
	/**
	 * Demande d'envoi du fichier
	 */
	@Override
	public void SendFile() {
		Object[] usersObj = listUser.getSelectedValues();
		if (usersObj.length == 0)
			listMess.setText(listMess.getText()+"\n"+ "Error : Select one user please.");
		else if (usersObj.length > 1)
			listMess.setText(listMess.getText()+"\n"+ "Error : Select only one user please.");
		else {
			String user = (String) usersObj[0];
			
			user = user.substring(0, user.indexOf('@'));
			
			File file = SelectFile();
			if (file != null)
				chatPres.fileToSend (user, file);
				//chatPres.propFileReceived("benjamin-Studio-XPS-1640.local", file.getName(), file.length(), 1);
			else
				listMess.setText(listMess.getText()+"\n"+ "Error : File not selected.");
		}
	}

	/**
	 * Reception d'une proposition de transfert de fichier
	 * Demande d'acceptation + indication du dossier d'enregistrement du fichier
	 * @param user
	 * @param filename
	 * @param filesize
	 * @return fichier reception
	 */
	public File propFileReceived(String user, String filename, long filesize) {
		File file = null;
		
		if (FileRequest(user, filename, filesize)) {
			file = ChooseFilePath(filename);
		}
		
		return file;
	}
	
	/**
	 * fenêtre de choix si oui ou non on veut enregistrer le fichier
	 * @param user
	 * @param filename
	 * @param filesize
	 * @return acceptFile
	 */
	public boolean FileRequest (String user, String filename, long filesize) {
		String Sfilesize = null;
		if (filesize > 1024*1024)
			Sfilesize = new String (filesize/(1024*1024)+"Mo");
		else if (filesize > 1024)
			Sfilesize = new String (filesize/1024+"ko");
		else
			Sfilesize = new String (filesize+"o");
		
		listMess.setText(listMess.getText()+"\n"+user+" want to send you the file "+filename+" (size : "+Sfilesize+").");
		
		//JOptionPane jop = new JOptionPane();			
		int option = JOptionPane.showConfirmDialog(null, "Do you want to download the file "+filename+" ("+Sfilesize+") from "+user+" ?", "Downloading File", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
		
		if (option == JOptionPane.YES_OPTION)
			return true;
		else
			return false;
	}
	
	/**
	 * choix de l'emplacement de destination
	 */
	public File ChooseFilePath(String filename) {
		File file = null;
		fileChooserBox = new JFileChooser();
		fileChooserBox.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fileChooserBox.setSelectedFile(new File (filename));
		fileChooserBox.setMultiSelectionEnabled(false);
		if (fileChooserBox.showSaveDialog(null)== 
		    JFileChooser.APPROVE_OPTION) {
			file = fileChooserBox.getSelectedFile();
		}
		
		return file;
	}
	
	/**
	 * Indication de l'envoi en cours d'un fichier
	 * @param username
	 * @param filename
	 */
	@Override
	public void FileIsSending(String username, String filename) {
		listMess.setText(listMess.getText()+"\n"+ "File is sending to "+username+": "+filename+".");
	}
	
	/**
	 * Indication de l'attente de l'utilisateur distant pour la reception d'un fichier
	 * @param username
	 * @param filename
	 */
	@Override
	public void FileIsWaiting(String username, String filename) {
		listMess.setText(listMess.getText()+"\n"+ "File is waiting to "+username+": "+filename+".");
	}
	
	/**
	 * Indication du refus de l'utilisateur distant de recevoir un fichier
	 * @param username
	 * @param filename
	 */
	public void FileRefused (String username, String filename) {
		listMess.setText(listMess.getText()+"\n"+username+" refused the file : "+filename+".");
	}
	
	/**
	 * Indication de la fin de l'envoi d'un fichier
	 * @param username
	 * @param filename
	 */
	@Override
	public void FileSent(String username, String filename) {
		listMess.setText(listMess.getText()+"\nFile sent to "+username+": "+filename+".");
	}
	
	/**
	 * Indication de l'envoi en cours d'un fichier
	 * @param username
	 * @param filename
	 */
	public void fileIsSending(String username, String filename) {
		FileIsSending (username, filename);
	}
	
	/**
	 * Indication de l'attente de l'utilisateur distant pour la reception d'un fichier
	 * @param username
	 * @param filename
	 */
	public void fileIsWaiting(String username, String filename) {
		FileIsWaiting (username, filename);
	}
	
	/**
	 * Indication du refus de l'utilisateur distant de recevoir un fichier
	 * @param username
	 * @param filename
	 */
	public void fileRefused(String username, String filename) {
		FileRefused (username, filename);
	}
	
	/**
	 * Indication de la fin de l'envoi d'un fichier
	 * @param username
	 * @param filename
	 */
	public void fileSent(String username, String filename) {
		FileSent(username, filename);
	}
	
	/**
	 * Indication de la reception d'un fichier
	 * @param filename
	 * @param absolutePath
	 */
	public void fileReceived(String filename, String absolutePath) {
		listMess.setText(listMess.getText()+"\nFile "+filename+" received in "+absolutePath+".");
	}
	
	/**
	 * Fermeture de tout les composants
	 */
	private void cleanLayout () {
		this.remove(messLogin);
		this.remove(textLogin);
		this.remove(bConnect);
		
		this.remove(listMess);
		this.remove(listUser);
		this.remove(messArea);
		this.remove(bSendFile);
		this.remove(bDisconnect);
	}
	
	/**
	 * Listener du bouton Login
	 * @author benjamen et david
	 *
	 */
	private class ConnectListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			Connect ();
		}
	}
	
	/**
	 * Listener du bouton Disconnect
	 * @author benjamen et david
	 *
	 */
	private class DisconnectListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			Disconnect ();
		}
	}
	
	/**
	 * Listener du bouton Send File
	 * @author benjamen et david
	 *
	 */
	private class SendFileListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			SendFile ();
		}
	}
}
